package com.nts.bupna.generator;

import static com.google.common.base.CaseFormat.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import com.google.common.base.Strings;
import com.nts.bupna.model.Activity;
import com.nts.bupna.model.ActivityType;
import com.nts.bupna.model.Assign;
import com.nts.bupna.model.If;
import com.nts.bupna.model.If.ElseIf;
import com.nts.bupna.model.Imports;
import com.nts.bupna.model.Invoke;
import com.nts.bupna.model.Invoke.MethodType;
import com.nts.bupna.model.Process;
import com.nts.bupna.model.Receive;
import com.nts.bupna.model.Reply;
import com.nts.bupna.model.Sequence;
import com.nts.bupna.model.Throw;
import com.nts.bupna.model.Var;
import com.nts.bupna.model.Vars;
import com.nts.bupna.model.While;

public class ProcessToJava extends Generator {

    static Logger log = LoggerFactory.getLogger(ProcessToJava.class);

    private final Process process;

    private final String locatorClass;

    public ProcessToJava(final Process process) {
        this(process, "");//no locator would require explicit (static) import in the process
    }

    public ProcessToJava(final Process process, final String locatorClass) {
        this.process = process;
        this.locatorClass = locatorClass;
    }

    public String generateJavaCode() {
        packageDeclaration();
        imports();
        classDeclaration();
        r();
        variables();
        method();
        l();
        classEnd();
        final String code = done();
        log.debug("\n{}", code);
        return code;
    }

    private void imports() {
        final Imports imports = process.getImports();
        if (imports != null) {
            for (final String line : imports) {
                line("import " + line + ";");
            }
            line();
        }
    }

    private void method() {
        final Receive receive = process.getReceive();
        final Reply reply = process.getReply();
        final String resultType = reply != null ? process.getVariable(reply.getResult()).getType() : "void";
        line("public " + resultType + " " + receive.getName() + "(" + methodParam(receive) + ") {");
        r();
        variableInitFromParam(receive.getVar());
        activity(process.getActivity());
        reply();
        l();
        line("}");
    }

    private void reply() {
        final Reply reply = process.getReply();
        if (reply != null) {
            line("return " + reply.getResult() + ";");
        }
    }

    private void activity(final Activity activity) {
        final ActivityType activityType = activity.getActivityType();
        switch (activityType) {
        case SEQUENCE:
            sequence((Sequence) activity);
            break;
        case INVOKE:
            invoke((Invoke) activity);
            break;
        case ASSIGN:
            assign((Assign) activity);
            break;
        case IF:
            ifActivity(activity);
            break;
        case WHILE:
            whileActivity(activity);
            break;
        case THROW:
            throwActivity(activity);
            break;
        }
    }

    private void throwActivity(Activity activity) {
        Throw theThrow = (Throw) activity;
        String error = theThrow.getError();
        line("throw " + error + ";");
    }

    private void whileActivity(final Activity activity) {
        final While theWhile = (While) activity;
        final String condition = theWhile.getCondition();
        line("while (" + replaceVars(condition) + ") {");
        r();
        activity(theWhile.getActivity());
        l();
        line("}");
    }

    private void ifActivity(final Activity activity) {
        final If theIf = (If) activity;
        String condition = theIf.getCondition();
        line("if (" + replaceVars(condition) + ") {");
        r();
        activity(theIf.getActivity());
        l();
        line("}");
        for (final ElseIf elseIf : theIf) {
            condition = elseIf.getCondition();
            line("else if (" + replaceVars(condition) + ") {");
            r();
            activity(elseIf.getActivity());
            l();
            line("}");
        }
        if (theIf.getElse() != null) {
            line("else {");
            r();
            activity(theIf.getElse().getActivity());
            l();
            line("}");
        }
    }

    static Pattern varPattern = Pattern.compile("\\$\\{([^\\}]+)\\}");

    private String replaceVars(final String condition) {
        final Matcher matcher = varPattern.matcher(condition);
        final StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            final String group = matcher.group();
            matcher.appendReplacement(sb, CopyUtil.toGetter(group.substring(2, group.length()-1)));
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

    private void assign(final Assign assign) {
        final String from = assign.getFrom();
        final String to = assign.getTo();
        final String expr = assign.getExpr();
        final String rhs = from != null ? CopyUtil.toGetter(from) : replaceVars(expr);
        final boolean assingment = CopyUtil.simple(to);
        if (assingment) {
            line(to + " = " + rhs + ";");
        } else {
            line(CopyUtil.toSetter(to) + "(" + rhs + ");");
        }
    }

    private void sequence(final Sequence sequence) {
        for (final Activity activity : sequence) {
            activity(activity);
        }
    }

    private void variableInitFromParam(final String varName) {
        line("this." + varName + " = " + varName + ";");
    }

    private void invoke(final Invoke invoke) {
        final StringBuilder call = new StringBuilder();
        final String type = invoke.getType();
        if (invoke.getService() != null) {
            final String locatorCall = Strings.isNullOrEmpty(locatorClass) ? "getService" : locatorClass + ".getService";
            final String lowerCamel = UPPER_CAMEL.to(LOWER_CAMEL, type);
            line(type + " " + lowerCamel + " = " + locatorCall + "(\"" + invoke.getService() + "\");");
            call.append(lowerCamel);
        } else if (MethodType.STATIC == invoke.getMethodType()) {
            call.append(type);
        } else {
            call.append("new ").append(type).append("()");
        }
        final String target = call.append('.').toString();
        if (invoke.getResult() == null) {
            line(target  + invoke.getMethod() + "(" + paramVars(invoke) + ");");
        } else {
            line(invoke.getResult() + " = " + target + invoke.getMethod() + "(" + paramVars(invoke) + ");");
        }
    }

    private String paramVars(Invoke invoke) {
        if (invoke.getVar() == null && invoke.getVars() == null) {
            return "";
        }
        return invoke.getVar() != null ? invoke.getVar() : invoke.getVars();

    }

    private String methodParam(final Receive receive) {
        final String varName = receive.getVar();
        final Var variable = process.getVariable(varName);
        return variable.getType() + " " + varName;
    }

    private void classEnd() {
        line("}");
    }

    private void variables() {
        final Vars vars = process.getVars();
        for (final Var var : vars) {
            if (var.getInit()) {
                line("private " + var.getType() + " " + var.getName() + " = new " + var.getType() + "();");
            } else {
                line("private " + var.getType() + " " + var.getName() + ";");
            }
        }
        line();
    }

    private void classDeclaration() {
        line("public class " + process.getName() + " { ");
    }

    private void packageDeclaration() {
        line("package " + process.getPackageName() + ";");
        line();
    }
}
